<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * This file contains the PDODB_Datatype class.
 *
 * PHP version 5
 *
 * This file is part of PEAR::PDODB - A database abstraction layer for PHP5 built
 * using PDO. PHP version 5.1+ is recommended.
 *
 * PEAR::PDODB is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * PEAR::PDODB is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with PEAR::PDODB. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package     PDODB
 * @category    Database
 * @author      Lukas Smith <smith@pooteeweet.org>
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 * @copyright   1998-2007 Manuel Lemos, Tomas V.V. Cox, Stig S. Bakken, Lukas Smith
 * @copyright   2007 Michael J. I. Jackson
 * @license     http://www.gnu.org/licenses/lgpl-3.0.txt GNU LGPL 3.0
 * @version     SVN: $Id: Datatype.php 2 2007-11-27 17:20:29Z mjijackson $
 * @link        http://pear.php.net/packages/PDODB
 */

/**
 * The base class for all data type module classes.
 *
 * @package     PDODB
 * @category    Database
 * @author      Lukas Smith <smith@pooteeweet.org>
 * @author      Michael J. I. Jackson <mjijackson@gmail.com>
 */
abstract class PDODB_Datatype extends PDODB_Module
{

    /**
     * Valid default values for various data types.
     *
     * @access  protected
     */
    protected $valid_default_values = array(
        'text'          => '',
        'boolean'       => true,
        'integer'       => 0,
        'decimal'       => 0.0,
        'float'         => 0.0,
        'timestamp'     => '1970-01-01 00:00:00',
        'time'          => '00:00:00',
        'date'          => '1970-01-01',
        'clob'          => '',
        'blob'          => '',
    );

    /**
     * Gets a list of valid default data types.
     *
     * @return  array           An array of valid default data types
     * @access  public
     */
    public function getValidTypes()
    {
        return $this->valid_default_values;
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a field of the
     * given type. For more information on field definitions, see
     * {@link PDODB_Manager::createTable()}.
     *
     * @param   string  $type       The type of the field
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field's definition
     * @return  string              The SQL code needed to declare the field
     * @throws  PDODB_UnknownDeclarationTypeException   If the field type is
     *                                                  unknown
     * @access  public
     */
    public function getDeclaration($type, $name, $field)
    {
        $method = "get{$type}Declaration";
        if (!method_exists($this, $method)) {
            throw new PDODB_UnknownDeclarationTypeException($type);
        }

        return $this->$method($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a generic type of
     * field. This declaration can be used in statements like CREATE TABLE.
     *
     * @param   string  $name       The name of the field to be declared
     * @param   array   $field      The field's definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function createDeclaration($name, $field)
    {
        $db = $this->getConnection();
        $name = $db->quoteIdentifier($name, true);

        return $name . ' ' . $this->getTypeDeclaration($field)
            . $this->getDeclarationOptions($field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare the type of a
     * field.
     *
     * @param   array   $field      The field definition
     * @return  string              The type to use in the field declaration
     * @access  public
     */
    public function getTypeDeclaration($field)
    {
        $db = $this->getConnection();

        switch ($field['type']) {
        case 'text':
            $d_length = $db->getAttribute(PDODB::ATTR_DEFAULT_TEXT_LENGTH);
            $length = !empty($field['length']) ? $field['length'] : $d_length;
            return (!empty($field['fixed']) ? $field['fixed'] : false)
                ? ($length ? 'CHAR(' . $length . ')' : 'CHAR(' . $d_length . ')')
                : ($length ? 'VARCHAR(' . $length . ')' : 'TEXT');
        case 'integer':
        case 'boolean':
            return 'INT';
        case 'date':
            return 'CHAR (' . strlen('YYYY-MM-DD') . ')';
        case 'time':
            return 'CHAR (' . strlen('HH:MM:SS') . ')';
        case 'timestamp':
            return 'CHAR (' . strlen('YYYY-MM-DD HH:MM:SS') . ')';
        case 'clob':
        case 'blob':
        case 'float':
        case 'decimal':
            return 'TEXT';
        }

        return '';
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to set the options for a
     * generic type field. Supported options include the CHARACTER SET, DEFAULT,
     * NULL, and COLLATION field settings.
     *
     * @param   array   $field      The field definition
     * @return  string              The field declaration options
     * @access  protected
     */
    protected function getDeclarationOptions($field)
    {
        $charset = empty($field['charset']) ? '' :
            ' ' . $this->getCharsetFieldDeclaration($field['charset']);

        $default = '';
        if (array_key_exists('default', $field)) {
            if ($field['default'] === '') {
                if (empty($field['notnull'])) {
                    $field['default'] = null;
                } else {
                    $valid_default_values = $this->getValidTypes();
                    $field['default'] = $valid_default_values[$field['type']];
                }
            }
            $default = ' DEFAULT ' . $this->getConnection()->quote($field['default']);
        } elseif (empty($field['notnull'])) {
            $default = ' DEFAULT NULL';
        }

        $notnull = empty($field['notnull']) ? '' : ' NOT NULL';

        $collation = empty($field['collation']) ? '' :
            ' ' . $this->getCollationFieldDeclaration($field['collation']);

        return $charset . $default . $notnull . $collation;
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to set the CHARACTER SET of
     * a field.
     *
     * @param   string  $charset        The character set to use
     * @return  string                  The CHARACTER SET portion of the field
     *                                  declaration
     * @access  protected
     */
    protected function getCharsetFieldDeclaration($charset)
    {
        return '';
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to set the COLLATION of a
     * field.
     *
     * @param   string  $collation      The collation to use
     * @return  string                  The COLLATION portion of the field
     *                                  declaration
     * @access  protected
     */
    protected function getCollationFieldDeclaration($collation)
    {
        return '';
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare an INTEGER type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getIntegerDeclaration($name, $field)
    {
        if (!empty($field['unsigned'])) {
            $db = $this->getConnection();
            $db->raiseWarning(get_class($this), __FUNCTION__,
                "Unsigned integer field '$name' is being declared as signed integer");
        }

        return $this->createDeclaration($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a TEXT type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getTextDeclaration($name, $field)
    {
        return $this->createDeclaration($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a BOOLEAN type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getBooleanDeclaration($name, $field)
    {
        return $this->createDeclaration($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a DATE type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getDateDeclaration($name, $field)
    {
        return $this->createDeclaration($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a TIMESTAMP type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getTimestampDeclaration($name, $field)
    {
        return $this->createDeclaration($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a TIME type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getTimeDeclaration($name, $field)
    {
        return $this->createDeclaration($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a FLOAT type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getFloatDeclaration($name, $field)
    {
        return $this->createDeclaration($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a DECIMAL type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getDecimalDeclaration($name, $field)
    {
        return $this->createDeclaration($name, $field);
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a CLOB type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getCLOBDeclaration($name, $field)
    {
        $db = $this->getConnection();
        $name = $db->quoteIdentifier($name, true);
        $type = $this->getTypeDeclaration($field);
        $null = empty($field['notnull']) ? '' : ' NOT NULL';

        return "$name {$type}{$null}";
    }

    /**
     * Gets a DBMS-specific SQL code portion needed to declare a BLOB type
     * field.
     *
     * @param   string  $name       The name the field to be declared
     * @param   array   $field      The field definition
     * @return  string              The field declaration
     * @access  protected
     */
    protected function getBLOBDeclaration($name, $field)
    {
        $db = $this->getConnection();
        $name = $db->quoteIdentifier($name, true);
        $type = $this->getTypeDeclaration($field);
        $null = empty($field['notnull']) ? '' : ' NOT NULL';

        return "$name {$type}{$null}";
    }

}

?>
